package com.hymjweb.demo.hymj.log_manage.aop.aspect;

import com.hymjweb.demo.framework.sys.SysConstant;
import com.hymjweb.demo.framework.util.DataTypeUtil;
import com.hymjweb.demo.hymj.log_manage.data_column.service.DataColumnService;
import com.hymjweb.demo.hymj.log_manage.aop.annotation.OperationLogger;
import com.hymjweb.demo.hymj.log_manage.service.XtBackupLogMsgService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Table;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Aspect
@Component
@Data
public class SysLogAspect {

    @Autowired
    private DataColumnService dataColumnService;

    @Autowired
    private XtBackupLogMsgService xtBackupLogMsgService;

    // http://www.yayihouse.com/yayishuwu/chapter/1686 需要匹配多个路径
    @Pointcut("@annotation(com.hymjweb.demo.hymj.log_manage.aop.annotation.OperationLogger)")
    public void serviceAspect(){

    }


    /**
     * 前置通知（Before advice） ：在某连接点（JoinPoint）之前执行的通知，但这个通知不能阻止连接点前的执行。
     * @param joinPoint 连接点
     */
    //@Before("serviceAspect()")
    public void doBefore(JoinPoint joinPoint) {

        log.info("=====SysLogAspect前置通知开始=====");
        //handleLog(joinPoint, null);
    }

    /**
     * 后通知（After advice） ：当某连接点退出的时候执行的通知（不论是正常返回还是异常退出）。
     * @param joinPoint 连接点
     */
    //@AfterReturning(pointcut = "serviceAspect()")
    public void doAfter(JoinPoint joinPoint) {
        log.info("=====SysLogAspect后置通知开始=====");
        //handleLog(joinPoint, null);
    }

    /**
     * 抛出异常后通知（After throwing advice） ： 在方法抛出异常退出时执行的通知。
     * @param joinPoint 连接点
     * @param e 异常处理对象
     */
    //@AfterThrowing(value = "serviceAspect()", throwing = "e")
    public void doAfter(JoinPoint joinPoint, Exception e) {
        log.info("=====SysLogAspect异常通知开始=====");
        //handleLog(joinPoint, e);
    }

    /**
     * 环绕通知（Around advice） ：包围一个连接点的通知，类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为，也可以选择不执行。
     * @param joinPoint  连接点
     */
    @Around("serviceAspect()")
    @Transactional
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1.获得注解对象
        OperationLogger operationLogger = getAnnotationObject(joinPoint);

        if (operationLogger == null) {
            return null;
        }

        // 2.调用操作数据库前，获取原始未改变的数据对象  pre-processing
        Object originalObj = this.handleLogBefore(joinPoint, operationLogger);

        // 3.执行原始方法
        Object o = joinPoint.proceed();

        // 4.如果是添加则参数就是对应的添加的对象，则直接构建一个列表传入原业的列表中去
        if(SysConstant.OPER_ADD.equals(operationLogger.oper())){
            Object[] originalObjArray = joinPoint.getArgs();
            List<Object> list = new ArrayList<>();
            for(Object obj : originalObjArray){
                list.add(obj);
            }
            originalObj = list;
        }
        // 5.调用操作数据库后，新老对象做对比，并加入日志  Post processing
        this.handleLogAfter(originalObj,operationLogger);

        return  o;
    }

    /**
     * 获取注解对象
     * @param joinPoint 连接点
     * @return 获取到的注解对象
     */
    private OperationLogger getAnnotationObject(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(OperationLogger.class);
        }
        return null;
    }

    /**
     * 调用操作数据库前：根据参数信息和注解信息OperationLogger获取旧的数据库信息
     * @param joinPoint 连接点
     * @param operationLogger 注解对象
     * @return
     */
    private Object handleLogBefore(JoinPoint joinPoint, OperationLogger  operationLogger) throws Exception{
        // 1.添加时没有原对象，则直接返回
        if(SysConstant.OPER_ADD.equals(operationLogger.oper())){
            return null;
        }

        // 如果表名为空，无法获取对应对象，不能插入日志，报错
        DataTypeUtil.isEmptyStrThenThrow(operationLogger.classType(),SysConstant.XT_DIC_ERROR_MSG.no_tablename.getName());
        Table table = (Table) operationLogger.classType().getAnnotation(Table.class);

        // 1.获取对象参数
        Object[] args = joinPoint.getArgs();

        // 2.更新或删除都有原来的对象，所以把原来对象获取到
        // 2.1 如果区分更新或删除对象时 id 的格式，有可能是批量更新或批量删除,id 的格式为 'id1' 或 'id1','id2','id3'
        String objIdValue = DataTypeUtil.toStringObject(DataTypeUtil.getIdValueFromObj(args[0]));
        if(objIdValue == null){
            return null;
        }

        if(!objIdValue.contains(",") && !objIdValue.contains("'")){
            objIdValue = DataTypeUtil.quoteStr(objIdValue);
        }
        // 获取操作之前的对象
        Object originalObject = dataColumnService.queryPojoByIds(table.name(),objIdValue,operationLogger.classType());

        return originalObject;

    }

    /**
     * 调用操作数据库后：对新老对象的比较，加入日志
     * @param originalObj
     * @param operationLogger
     * @throws Exception
     */
    private void handleLogAfter(Object originalObj,OperationLogger operationLogger) throws Exception{
        if(originalObj instanceof List){
            List originalObjectList = (List) originalObj;
            for(Object originalObject : originalObjectList){
                //operationLogger.oper(),operationLogger.operTitle(),operationLogger.operMemo(),operationLogger.classType().getName()
                xtBackupLogMsgService.addLogDirect(originalObject,operationLogger);
            }
        }
    }



}
